package home.xbee;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import com.rapplogic.xbee.api.ApiId;
import com.rapplogic.xbee.api.AtCommand;
import com.rapplogic.xbee.api.AtCommandResponse;
import com.rapplogic.xbee.api.PacketListener;
import com.rapplogic.xbee.api.RemoteAtRequest;
import com.rapplogic.xbee.api.RemoteAtResponse;
import com.rapplogic.xbee.api.XBee;
import com.rapplogic.xbee.api.XBeeAddress64;
import com.rapplogic.xbee.api.XBeeException;
import com.rapplogic.xbee.api.XBeeRequest;
import com.rapplogic.xbee.api.XBeeResponse;
import com.rapplogic.xbee.api.XBeeTimeoutException;
import com.rapplogic.xbee.api.zigbee.NodeDiscover;
import com.rapplogic.xbee.api.zigbee.NodeDiscover.DeviceType;
import com.rapplogic.xbee.api.zigbee.ZNetRxIoSampleResponse;
import com.rapplogic.xbee.api.zigbee.ZNetTxRequest;
import com.rapplogic.xbee.util.ByteUtils;



public class XBeeNetwork {
		
	
	// The results from Node Discovery are stored in listAllNodes, listOfEndNodes, and listRouters,
	//  according to their Device Type.  listAllNodes contains a comprehensive list of the network.
	// The AT commands to do different things (node discovery, request analog sample from pin D0,
	//  change power level, etc.) are stored in the other 3 lists.
	List <XBeeData>        listOfAllNodes;
	List <XBeeData>        listOfEndNodes;
	List <XBeeData>        listOfRouters;
	List <RemoteAtRequest> listOfRemoteAtRequests;
	List <ZNetTxRequest>   listOfZNetTxRequests;
	List <XBeeRequest>     listOfForceSampleRequest;

	/*  Constructor */
	public XBeeNetwork(	List<XBeeData> allNodes,
						List<XBeeData> endNodes, 
						List<XBeeData> routers,
						List <RemoteAtRequest> remoteATrequests, 
						List <ZNetTxRequest> znetTXrequests,
						List <XBeeRequest> forceSampleRequests){
		this.listOfAllNodes = allNodes;
		this.listOfEndNodes = endNodes;
		this.listOfRouters = routers;
		this.listOfRemoteAtRequests = remoteATrequests;
		this.listOfZNetTxRequests = znetTXrequests;
		this.listOfForceSampleRequest = forceSampleRequests;
	}

	/* Set up logging. */
	private final static Logger log = Logger.getLogger(XBeeNetwork.class);
	
	/* There will only be one network. */
	private final static XBeeNetwork network = new XBeeNetwork(
			new ArrayList<XBeeData>(),
			new ArrayList<XBeeData>(),
			new ArrayList<XBeeData>(),
			new ArrayList<RemoteAtRequest>(),
			new ArrayList<ZNetTxRequest>(),
			new ArrayList<XBeeRequest>()
			);

	/***** Begin definition of each of the packets which will be used here. *****/
	/****************************************************************************
	 * The inner method of PacketListeners, "processResponse", is called by the *
	 * XBee API thread when a packet is received.                               *
	 ****************************************************************************/	
	/* Define a transmission of the D0 packet (analog sample from pin D0).*/                                 
	static PacketListener D0 = new PacketListener(){
		public void processResponse(XBeeResponse response) {
			if (response.getApiId()== ApiId.ZNET_IO_SAMPLE_RESPONSE){
				ZNetRxIoSampleResponse ioSample = (ZNetRxIoSampleResponse) response;
				log.debug("received i/o sample packet.  Contains analog = " + ioSample.containsAnalog() + ", contains digital = " + ioSample.containsDigital());
				log.debug("pin 20 (D0) analog is " + ioSample.getAnalog(0));
			}
		}
	}; /* End of D0 packet description */
	
	/* Prepare the Node Discover packet for transmission. */
	static PacketListener ND = new PacketListener(){
		public void processResponse(XBeeResponse response) {
			if (response.getApiId() == ApiId.AT_RESPONSE) {

				/* create an XBeeData container to hold the 64-bit address and corresponding node id. */
				XBeeData xbData = new XBeeData();
				NodeDiscover ndResponse = NodeDiscover.parse((AtCommandResponse)response);
				log.info("Node discover response is: " + ndResponse);

				xbData.addr = ndResponse.getNodeAddress64();
				xbData.nodeId = ndResponse.getNodeIdentifier();
				
				/* When each node replies, it will be added to two lists:
				 * 1. The list of all nodes
				 * 2. The corresponding list of either end nodes or routers */
				
				network.listOfAllNodes.add(  xbData  );
				if(  ndResponse.getDeviceType() == DeviceType.DEV_TYPE_END_DEVICE  ){
					network.listOfEndNodes.add(  xbData  );
					log.info(  "Added " + xbData.nodeId + " to listOfEndNodes."  );
				} else if(  ndResponse.getDeviceType() == DeviceType.DEV_TYPE_ROUTER  ){
					network.listOfRouters.add(  xbData  );
					log.info(  "Added " + xbData.nodeId + " to listOfRouters."  );
				}
			} else {
				log.debug(  "Ignoring unexpected response: " + response  );
			}
		}
	}; /* End Node Discover packet description. */
	/***** End definition of the packets in use here. *****/

	
	/* @throws InterruptedException 
	 * @throws XBeeException */
	public static void do_ND(XBee xbee, long nodeDiscoveryTimeout) throws InterruptedException, XBeeException{
		/* Phase 1.1: Broadcast ND command. */
		xbee.addPacketListener(ND);
		log.info("Sending node discover command");
		xbee.sendAsynchronous(new AtCommand("ND"));

		Thread.sleep(nodeDiscoveryTimeout);
		log.debug("Time's up!  You should've heard back from all nodes by now.");

		/* Phase 1.2: Print out responses. */
		log.info("Results of node discovery: ");
		log.info("End nodes: ");
		for(int i = 0; i<network.listOfEndNodes.size(); i++){
			log.info("Node " + network.listOfEndNodes.get(i).nodeId + " addr is " + network.listOfEndNodes.get(i).addr);
		}
		log.info("Routers: ");
		for(int i = 0; i<network.listOfRouters.size(); i++){
			log.info("Node " + network.listOfRouters.get(i).nodeId + " addr is " + network.listOfRouters.get(i).addr);
		}
		/* Phase 1.3: Clean up all the Node Discover business here, get ready for next step. */
		xbee.clearResponseQueue();
		xbee.removePacketListener(ND);
	}

	
	public static long do_NT(XBee xbee) throws XBeeException{
		xbee.sendAsynchronous(new AtCommand("NT"));
		AtCommandResponse nodeTimeout = (AtCommandResponse) xbee.getResponse();

		/* Default timeout is 6 seconds */
		long nodeDiscoveryTimeout = ByteUtils.convertMultiByteToInt(nodeTimeout.getValue()) * 100;
		log.debug("Node discovery timeout is " + nodeDiscoveryTimeout + " milliseconds");
		
		return nodeDiscoveryTimeout;
	}


	public static void do_PL(XBee xbee, long nodeDiscoveryTimeout, int powerLevel) throws XBeeTimeoutException, XBeeException{
		/* Staging : generate and prepare the transmissions here. */
		for(int i=0; i<network.listOfAllNodes.size(); i++){
			network.listOfRemoteAtRequests.add(new RemoteAtRequest((XBeeAddress64) network.listOfAllNodes.get(i).addr, "PL", new int[] {powerLevel}));
			log.debug("Building command to set power level to " + powerLevel + " for " + network.listOfAllNodes.get(i).nodeId);
		}
		/* Deploy : transmit the messages in the order they were generated */
		for(int i=0; i<network.listOfRemoteAtRequests.size(); i++){
			if(((RemoteAtResponse) xbee.sendSynchronous(network.listOfRemoteAtRequests.get(i), (int) nodeDiscoveryTimeout)).isOk()){
				log.info(network.listOfAllNodes.get(i).nodeId + " is now transmitting at power level " + powerLevel + ".");
			} else {
				throw new RuntimeException("Failed to configure power level on " + network.listOfAllNodes.get(i).nodeId);
			}
		}
		/* Cleanup. Prepare for the next round of AT requests */
		xbee.clearResponseQueue();
		network.listOfRemoteAtRequests.clear();       
	}
	
	
	public static void do_configure_pins(XBee xbee, long nodeDiscoveryTimeout, String command, int[] parameters) throws XBeeTimeoutException, XBeeException{
		xbee.addPacketListener(D0);
		/* Construct and store request packets. */
		for(int i=0; i<network.listOfEndNodes.size(); i++){
			network.listOfRemoteAtRequests.add(new RemoteAtRequest((XBeeAddress64) network.listOfEndNodes.get(i).addr, command, parameters));
			log.debug("Creating ATD0 command for " + network.listOfEndNodes.get(i).nodeId);
		}
		/* Phase 3.2: Send out the requests just generated, and store the responses. */
		for(int i=0; i<network.listOfRemoteAtRequests.size(); i++){
			if(((RemoteAtResponse) xbee.sendSynchronous(network.listOfRemoteAtRequests.get(i), (int) nodeDiscoveryTimeout)).isOk()){
				log.info("Pin 20 (D0) on " + network.listOfEndNodes.get(i).nodeId + " is now configured for analog input.");
			} else {
				throw new RuntimeException("Failed to configure D0 on " + network.listOfEndNodes.get(i).nodeId);
			}
		}
		/* Phase 3.3: Clean up; prepare for next step. */
		network.listOfRemoteAtRequests.clear();       
		xbee.clearResponseQueue();
	}
	
	
	public static void main (String[] args) throws Exception{
		BasicConfigurator.configure();
		PropertyConfigurator.configure("log4j.properties");
		Logger.getRootLogger().setLevel(Level.INFO);

		XBee xbee = new XBee();
		try{
			xbee.open("COM13", 9600);

			/* get the Node Timeout from the connected controller node */
			long nodeDiscoveryTimeout = do_NT(xbee);

			/* Step 1: Broadcast a Node Discover command, then listen for and process the responses. */
			do_ND(xbee, nodeDiscoveryTimeout);

			/* Step 2: set power level = 4 (highest). */
			do_PL(xbee, nodeDiscoveryTimeout, 4);
		
			/* Step 3: Configure D0 pins on the end nodes for analog input, and prepare to parse analog response packets. */
			final int ANALOG_INPUT = 2;
			final int[] d0_parameters = new int[] {ANALOG_INPUT};
			do_configure_pins(xbee, nodeDiscoveryTimeout, "D0", d0_parameters);

			/***** Step 4: Send data from Coordinator to End Nodes. *****/
			/* Phase 4.1: Construct the payload. */
			int[] payload = ByteUtils.stringToIntArray("Alligators");
			for(int i=0; i<network.listOfEndNodes.size(); i++){
				network.listOfZNetTxRequests.add(new ZNetTxRequest(network.listOfEndNodes.get(i).addr, payload));
				log.debug("Creating data payload for " + network.listOfEndNodes.get(i).nodeId);                      
			}


			//  All the prep work is done at this point. Loop through this ad infinitum, collecting RSSI data.
			while(true){

				for(int i=0; i<network.listOfEndNodes.size(); i++){
					xbee.sendAsynchronous(network.listOfZNetTxRequests.get(i));
					log.info("Data packet sent to " + network.listOfEndNodes.get(i).nodeId);

					xbee.clearResponseQueue();
				}
				Thread.sleep(nodeDiscoveryTimeout);
			} // end while(true)
		} // end of the try block from the very beginning
		finally {
			xbee.close();
		}
	}




}